﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace OpenWaves.Web.Controls
{
    [ParseChildren(true), PersistChildren(false)]
    public abstract class TreeView<T> : HierarchicalDataBoundControl where T : ITreeNode<T>
    {
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public ITemplate Template { get; set; }

        private int maxNestingLevel = int.MaxValue;
        public int MaxNestingLevel
        {
            get { return this.maxNestingLevel; }
            set
            {
                Contract.Requires<ArgumentOutOfRangeException>(value > 0);
                this.maxNestingLevel = value;
            }
        }

        private object dataSource;
        public override object DataSource
        {
            get { return this.dataSource; }
            set
            {
                this.dataSource = value;
                if (IsValidListOfNodes(value) == false)
                    base.DataSource = value;
            }
        }


        protected static bool IsValidListOfNodes(object value)
        {
            if (value == null)
                return false;

            return value is IEnumerable<T>;
        }

        public override void DataBind()
        {
            base.DataBind();

            this.CreateControlsHierarchy();
        }

        protected override void Render(HtmlTextWriter writer)
        {
            this.RenderContents(writer);
        }

        protected override void CreateChildControls()
        {
            throw new NotSupportedException(ExceptionMessage.Format(
                "{0} does not support post scenarios when it is not rebound to a datasource. To avoid this exception, call DataBind() on {0} control also on post-backs.", this.GetType()));
        }

        private void CreateControlsHierarchy()
        {
            this.Controls.Clear();

            var rootNodes = this.GetRootNodes();
            var rootNodesPlaceholders = this.CreateRootNodesPlaceHolder();
            foreach (var nodesPlaceHolder in rootNodesPlaceholders)
            {
                var nodes = nodesPlaceHolder.GetNodes(rootNodes);
                if (nodesPlaceHolder.Visible && nodes.Any(this.IsNodeVisible))
                {
                    this.RecursivlyCreateControls(nodesPlaceHolder.ItemsContainer, nodes, 1);
                }
                else
                    nodesPlaceHolder.Visible = false;
            }

            this.ChildControlsCreated = true;
        }

        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
        protected IEnumerable<T> GetRootNodes()
        {
            var rootNodes = this.DataSource as IEnumerable<T>;
            if (rootNodes == null)
            {
                try
                {
                    rootNodes = this.AdaptHierarchicalEnumerable(this.GetData(null).Select());
                }
                catch (Exception e)
                {
                    throw new InvalidOperationException(
                        "An attempted to bind to hierarchical data source failed. The attempt was a made because supplied DataSource was null or it was not assignable to IEnumerable<T>.",
                        e);
                }
            }
            return rootNodes;
        }

        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        private IEnumerable<NodesPlaceHolderBase> CreateRootNodesPlaceHolder()
        {
            IEnumerable<NodesPlaceHolderBase> rootNodesPlaceholders;
            if (Template != null)
            {
                var templateContainer = new PlaceHolder();
                this.Template.InstantiateIn(templateContainer);
                this.Controls.Add(templateContainer);
                templateContainer.DataBind();

                rootNodesPlaceholders = FindNodesPlaceholders(templateContainer);
            }
            else
            {
                var childNodesPlaceHolder = new ChildNodesPlaceHolder();
                this.Controls.Add(childNodesPlaceHolder);
                rootNodesPlaceholders = new[] { childNodesPlaceHolder };
            }
            return rootNodesPlaceholders;
        }

        private void RecursivlyCreateControls(Control placeHolder, IEnumerable<T> nodes, int nestingLevel)
        {
            int nodeIndex = 0;
            foreach (var node in nodes.Where(this.IsNodeVisible))
            {
                var nodeTemplate = this.ResolveNodeTemplate(node, nestingLevel);
                if (nodeTemplate != null)
                {
                    var nodeContainer = this.CreateNodeContainer(node, nodeIndex, nestingLevel);

                    nodeTemplate.InstantiateIn(nodeContainer);
                    placeHolder.Controls.Add(nodeContainer);
                    nodeContainer.DataBind();

                    var childNodesPlaceholder = FindNodesPlaceholders(nodeContainer);

                    foreach (var nodesPlaceHolder in childNodesPlaceholder)
                    {
                        var children = nodesPlaceHolder.GetNodes(node.Children);
                        if (nodesPlaceHolder.Visible && nestingLevel < this.MaxNestingLevel && children.Any(this.IsNodeVisible))
                        {
                            this.RecursivlyCreateControls(nodesPlaceHolder.ItemsContainer, children, nestingLevel + 1);
                        }
                        else
                        {
                            nodesPlaceHolder.Visible = false;
                        }
                    }
                }

                nodeIndex++;
            }
        }

        private static IEnumerable<NodesPlaceHolderBase> FindNodesPlaceholders(Control nodeContainer)
        {
            return nodeContainer.GetChildrenRecursively()
                .OfType<NodesPlaceHolderBase>()
                .ToList();
        }

        protected bool NodeHasVisibleChildren(T node)
        {
            return node.Children.Any(this.IsNodeVisible);
        }

        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "FxCop is stupid and does not work well with CodeContracts.")]
        protected virtual TreeNodeContainer<T> CreateNodeContainer(T node, int nodeIndex, int nodeLevel)
        {
            Contract.Ensures(Contract.Result<TreeNodeContainer<T>>() != null);

            return new TreeNodeContainer<T>(node, nodeIndex, nodeLevel, node.Children.Any(this.IsNodeVisible));
        }

        protected virtual IEnumerable<T> AdaptHierarchicalEnumerable(IHierarchicalEnumerable hierarchicalEnumerable)
        {
            Contract.Ensures(Contract.Result<IEnumerable<T>>() != null);

            throw new NotSupportedException("This control does not support binding to hierarchical data sources.");
        }

        protected abstract ITemplate ResolveNodeTemplate(T node, int level);

        protected virtual bool IsNodeVisible(T node)
        {
            return true;
        }

        [Obsolete("Use IsNodeVisible instead.", true)]
        protected virtual bool RenderPredicate(T node)
        {
            throw new NotSupportedException("Obsolete, Use IsNodeVisibleInstead.");
        }
    }
}